#include "POMModel.h"

//Use geo gen to create models
#include "GeometryGenerator.h"

//Need shader manager to get access to effect
#include "ShaderManager.h"

//Complete forward declarations
#include "ParallaxOcclusionMappingFX.h"

POMModel::POMModel()
{
	//Init pointers to NULL.
	vb = 0;
	ib = 0;
	texMap = 0;
	normalHeightMap = 0;
	sepHeightMap = 0;
	specMap = 0;
	glossMap = 0;

	//Get a pointer to the effect we are using
	effect = ShaderManager::GetInstance()->ReturnPOMFX();

	//Graphics interface will do frustum culling. 
	subclassManagesFrustumCulling = false;

	//init tex transform. 
	XMStoreFloat4x4(&texTransformation, XMMatrixScaling(1.0f, 1.0f, 1.0f));

	//Default material
	material = Material();

	//Init with default POM data
	heightScalePOM = 0.07f;
	minSamplesPOM = 5;
	maxSamplesPOM = 14;

	//Default to false - use normal map as heighfield. 
	isUsingSepHM = false;

	//Wont be using by default
	isUsingSpecGloss = false;
}

POMModel::~POMModel()
{
	//Release D3D resources
	ReleaseCOM(vb);
	ReleaseCOM(ib);
	ReleaseCOM(texMap);
	ReleaseCOM(normalHeightMap);
	ReleaseCOM(sepHeightMap);
	ReleaseCOM(specMap);
	ReleaseCOM(glossMap);
}

void POMModel::InitAsGrid(ID3D11Device* device, 
	float width, float depth, unsigned vM, unsigned vN, 
	LPCWSTR textureMapFile, LPCWSTR normalHeightFile, 
	LPCWSTR seperateHMFile, bool shouldUseSepHM, 
	LPCWSTR specMapFile, LPCWSTR glossMapFile, 
	bool shouldUseSpecGlossMap)
{
	//Do we use seperte HM
	isUsingSepHM = shouldUseSepHM;

	//Are we using gloss map / spec map
	isUsingSpecGloss = shouldUseSpecGlossMap;

	//Load texture map
	HR(D3DX11CreateShaderResourceViewFromFile(device, 
		textureMapFile, 0, 0, &texMap, 0));

	//Load Normal map & HM
	HR(D3DX11CreateShaderResourceViewFromFile(device, 
		normalHeightFile, 0, 0, &normalHeightMap, 0));

	//If we are using a seperte HM, load
	if (isUsingSepHM)
		HR(D3DX11CreateShaderResourceViewFromFile(device, 
			seperateHMFile, 0, 0, &sepHeightMap, 0));

	//Load spec and gloss map
	if (shouldUseSpecGlossMap)
	{
		HR(D3DX11CreateShaderResourceViewFromFile(device, 
			specMapFile, 0, 0, &specMap, 0));
	
		HR(D3DX11CreateShaderResourceViewFromFile(device, 
			glossMapFile, 0, 0, &glossMap, 0));
	}

	//Using geo generator, create a grid. 
	GeometryGenerator::MeshData data;
	GeometryGenerator geoGen;
	geoGen.CreateGrid(width, depth, vM, vN, data);

	//Extract Vertex info.
	std::vector<ParallaxOcclusionMappingFX::PerVertex>verts(data.Vertices.size());

	for (unsigned i = 0; i < data.Vertices.size(); i++)
	{
		verts[i].PosL = data.Vertices[i].Position;
		verts[i].NormalL = data.Vertices[i].Normal;
		verts[i].Tex = data.Vertices[i].TexC;
		verts[i].TangentL = data.Vertices[i].TangentU;

		//Also store position in sys copy
		sysCopyVerts.push_back(data.Vertices[i].Position);
	}

	//Create VB
	D3D11_BUFFER_DESC bd;
	bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	bd.ByteWidth = sizeof(ParallaxOcclusionMappingFX::PerVertex)*data.Vertices.size();
	bd.CPUAccessFlags = 0;
	bd.MiscFlags = 0;
	bd.StructureByteStride = 0;
	bd.Usage = D3D11_USAGE_IMMUTABLE;

	D3D11_SUBRESOURCE_DATA sd;
	sd.pSysMem = &verts[0];

	//Create GPU memory.
	HR(device->CreateBuffer(&bd, &sd, &vb));

	//Create IB
	bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
	bd.ByteWidth = sizeof(UINT) * data.Indices.size();
	sd.pSysMem = &data.Indices[0];

	//Create GPU memory.
	HR(device->CreateBuffer(&bd, &sd, &ib));

	//Store index count
	indexCount = data.Indices.size();

	//Create AABB
	GenerateAABB();

	//Is init
	isInit = true;
}

void POMModel::InitAsBox(ID3D11Device* device, 
	float width, float height, float depth, 
	LPCWSTR textureMapFile, LPCWSTR normalHeightFile, 
	LPCWSTR seperateHMFile, bool shouldUseSepHM, 
	LPCWSTR specMapFile, LPCWSTR glossMapFile,
	bool shouldUseSpecGlossMap)
{
	//Do we use seperte HM
	isUsingSepHM = shouldUseSepHM;

	//Are we using gloss map / spec map
	isUsingSpecGloss = shouldUseSpecGlossMap;

	//Load texture map
	HR(D3DX11CreateShaderResourceViewFromFile(device, 
		textureMapFile, 0, 0, &texMap, 0));

	//Load Normal map & HM
	HR(D3DX11CreateShaderResourceViewFromFile(device, 
		normalHeightFile, 0, 0, &normalHeightMap, 0));

	//If we are using a seperte HM, load
	if (isUsingSepHM)
		HR(D3DX11CreateShaderResourceViewFromFile(device, 
			seperateHMFile, 0, 0, &sepHeightMap, 0));

	//Load spec and gloss map
	if (isUsingSpecGloss)
	{
		HR(D3DX11CreateShaderResourceViewFromFile(device, 
			specMapFile, 0, 0, &specMap, 0));
	
		HR(D3DX11CreateShaderResourceViewFromFile(device, 
			glossMapFile, 0, 0, &glossMap, 0));
	}

	//Using geo generator, create a grid. 
	GeometryGenerator::MeshData data;
	GeometryGenerator geoGen;
	geoGen.CreateBox(width, height, depth, data);

	//Extract Vertex info.
	std::vector<ParallaxOcclusionMappingFX::PerVertex>verts(data.Vertices.size());

	for (unsigned i = 0; i < data.Vertices.size(); i++)
	{
		verts[i].PosL = data.Vertices[i].Position;
		verts[i].NormalL = data.Vertices[i].Normal;
		verts[i].Tex = data.Vertices[i].TexC;
		verts[i].TangentL = data.Vertices[i].TangentU;

		//Also store position in sys copy
		sysCopyVerts.push_back(data.Vertices[i].Position);
	}

	//Create VB
	D3D11_BUFFER_DESC bd;
	bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	bd.ByteWidth = sizeof(ParallaxOcclusionMappingFX::PerVertex)*data.Vertices.size();
	bd.CPUAccessFlags = 0;
	bd.MiscFlags = 0;
	bd.StructureByteStride = 0;
	bd.Usage = D3D11_USAGE_IMMUTABLE;

	D3D11_SUBRESOURCE_DATA sd;
	sd.pSysMem = &verts[0];

	//Create GPU memory.
	HR(device->CreateBuffer(&bd, &sd, &vb));

	//Create IB
	bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
	bd.ByteWidth = sizeof(UINT) * data.Indices.size();
	sd.pSysMem = &data.Indices[0];

	//Create GPU memory.
	HR(device->CreateBuffer(&bd, &sd, &ib));

	//Store index count
	indexCount = data.Indices.size();

	//Create AABB
	GenerateAABB();

	//Is init
	isInit = true;
}

void POMModel::SetMaterial(Material& m)
{
	//Copy material.
	material = m;

	//Ensure diffuse.a == 1.0f (No alpha blending)
	if (material.Diffuse.w != 1.0f)
	{
		OutputDebugStringW(L"POM Model Warning: Forcing Material::Diffuse.w to 1.0f.\n");
		material.Diffuse.w = 1.0f;
	}
}

void POMModel::SetTextureTransform(XMFLOAT2 scale, XMFLOAT2 translation)
{
	//Create texture transform.
	XMStoreFloat4x4(&texTransformation, 
		XMMatrixScaling(scale.x, scale.y, 1.0f) * 
		XMMatrixTranslation(translation.x, translation.y, 0.0f));
}

void POMModel::SetPOMData(float heightScale, int minSamples, int maxSamples)
{
	heightScalePOM = heightScale;
	minSamplesPOM = minSamples;
	maxSamplesPOM = maxSamples;
}

void POMModel::Render(bool isZPrePass, bool isOpaquePass, bool isShadowPass,		   
	VirtualCamera* playerCamera,										 
	DirecLightShadowMapData* shadowMap,									   
	SSAOData* ssaoMap,													  
	ID3D11ShaderResourceView* envMap,									  
	ID3D11ShaderResourceView* llb,                                       
	ID3D11ShaderResourceView* llibO, ID3D11ShaderResourceView* llsebO,     
	ID3D11ShaderResourceView* llibT, ID3D11ShaderResourceView* llsebT,     
	DirectionalLight* dLightArr, unsigned dLightCount,					  
	XMFLOAT3 &hemiColDown, XMFLOAT3 &hemiColRange,						   
	CXMMATRIX &view, CXMMATRIX &proj,									   
	ID3D11DeviceContext* context)
{
	//If shadow pass or not opaque pass (Ie, transparent pass), then return early
	if ( (isShadowPass) || (!isOpaquePass) )
		return;

	//Calculate matricies
	XMMATRIX world = XMLoadFloat4x4(&worldMatrix);
	XMMATRIX worldInvTrans = MathHelper::InverseTranspose(world);
	XMMATRIX worldView = world * playerCamera->GetView();
	XMMATRIX worldViewProj = worldView * playerCamera->GetProjection();

	//SSAO transform
	XMMATRIX ssaoTrans = worldViewProj * TO_TEX_SPACE;

	//Must be either the pre pass or opaque rendering pass - therefore, render the
	//geometry.
	//
	//Set IA stage
	UINT stride = sizeof(ParallaxOcclusionMappingFX::PerVertex);
	UINT offset = 0;

	context->IASetInputLayout(effect->ReturnInputLayout());
	context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
	context->IASetVertexBuffers(0, 1, &vb, &stride, &offset);
	context->IASetIndexBuffer(ib, DXGI_FORMAT_R32_UINT, 0);

	//Set active tehcnique. 
	if (isZPrePass)
		effect->SetActiveTechniqueByName("ZPrePass_AlphaClip");
	else  
	{	
		//Is Shading pass (Opaque)
		//
		//If using normal map as HM too (.a). Else, use seperte HM. 
		if (!isUsingSepHM)
		{
			if (isUsingSpecGloss)
				effect->SetActiveTechniqueByName("POM_AlphaClip_GlossSpec");
			else
				effect->SetActiveTechniqueByName("POM_AlphaClip");
		}
		else
		{
			if (isUsingSpecGloss)
				effect->SetActiveTechniqueByName("POM_AlphaClip_SepHM_GlossSpec");
			else
				effect->SetActiveTechniqueByName("POM_AlphaClip_SepHM");
		}
	}

	//Set effect variables.
	//
	//Per object cbuffer
	effect->SetCBufferPerObject(world, worldInvTrans, worldViewProj, 
		XMLoadFloat4x4(&texTransformation), 
		material, worldView);

	//Cube mapping.
	effect->SetEnvironmentMap(envMap);

	//Ambient
	effect->SetHemisphericalData(hemiColDown, hemiColRange);

	//Lighting.
	effect->SetLightingData(playerCamera->GetPosition(), 
		dLightArr, dLightCount, 
		llibO, llsebO);      //Opaque

	//Textures.
	effect->SetShaderResources(texMap, normalHeightMap, sepHeightMap, 
		specMap, glossMap);

	//Shadow mapping.
	effect->SetShadowMap(shadowMap->GetShadowMapSRV());
	effect->SetShadowMapMatricies(shadowMap->GetLightView(), shadowMap->GetLightProj(),
		shadowMap->GetNDCToTex());

	//SSAO
	effect->SetSSAOData(ssaoMap->ReturnSSAOMap(), ssaoTrans);

	//POM Data
	effect->SetPOMData(heightScalePOM, minSamplesPOM, maxSamplesPOM);

	//Draw model
	for (unsigned i = 0; i < effect->ReturnActiveTechniqueNumberOfPasses(); i++)
	{
		//Apply effect states
		effect->ApplyActiveTechniqueForPass(i, context);

		//Draw indexed
		context->DrawIndexed(indexCount, 0, 0);
	}

	//Unbind resources
	ID3D11ShaderResourceView* srvNULL[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
	context->PSSetShaderResources(0, 10, srvNULL);
}
